package net.qiqbframework.commandhandling.gateway;

import net.qiqbframework.commandhandling.*;
import net.qiqbframework.commandhandling.callbacks.FailureLoggingCallback;
import net.qiqbframework.commandhandling.callbacks.FutureCallback;
import net.qiqbframework.common.Registration;
import net.qiqbframework.messaging.intercepting.MessageDispatchInterceptor;
import lombok.extern.slf4j.Slf4j;

import javax.annotation.Nonnull;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

import static net.qiqbframework.common.BuilderUtils.assertNonNull;


@Slf4j
public class DefaultCommandGateway extends AbstractCommandGateway implements CommandGateway {

    private final CommandCallback<Object, Object> commandCallback;


    protected DefaultCommandGateway(Builder builder) {
        super(builder);
        this.commandCallback = builder.commandCallback;
    }


    public static Builder builder() {
        return new Builder();
    }

    @Override
    public <C, R> void send(@Nonnull C command, @Nonnull CommandCallback<? super C, ? super R> callback) {
        super.send(command, callback);
    }

    public <C, R> void batchSend(@Nonnull List<?> commands ) {
        super.batchSend(commands);
    }


    @Override
    @SuppressWarnings("unchecked")
    public <R> R sendAndWait(@Nonnull Object command) {
        FutureCallback<Object, R> futureCallback = new FutureCallback<>();
        send(command, futureCallback);
        CommandResultMessage<? extends R> commandResultMessage = futureCallback.getResult();
        if (commandResultMessage.isExceptional()) {
            throw asRuntime(commandResultMessage.exceptionResult());
        }
        return commandResultMessage.getPayload();
    }

    @Override
    @SuppressWarnings("unchecked")
    public <R> R sendAndWait(@Nonnull Object command, long timeout, @Nonnull TimeUnit unit) {
        FutureCallback<Object, R> futureCallback = new FutureCallback<>();
        send(command, futureCallback);
        CommandResultMessage<? extends R> commandResultMessage = futureCallback.getResult(timeout, unit);
        if (commandResultMessage.isExceptional()) {
            throw asRuntime(commandResultMessage.exceptionResult());
        }
        return commandResultMessage.getPayload();
    }

    @Override
    public <R> R batchSendAndWait(@Nonnull List<?> command) {
       // FutureCallback<Object, R> futureCallback = new FutureCallback<>();
        batchSend(command);

        return null;
    }

    @SuppressWarnings("unchecked") // Cast for commandCallback wrap
    @Override
    public <R> CompletableFuture<R> send(@Nonnull Object command) {
        FutureCallback<Object, R> callback = new FutureCallback<>();
        send(command, callback.wrap((CommandCallback<Object, R>) commandCallback));
        CompletableFuture<R> result = new CompletableFuture<>();
        callback.exceptionally(GenericCommandResultMessage::asCommandResultMessage)
                .thenAccept(r -> {
                    try {
                        if (r.isExceptional()) {
                            result.completeExceptionally(r.exceptionResult());
                        } else {
                            result.complete(r.getPayload());
                        }
                    } catch (Exception e) {
                        result.completeExceptionally(e);
                    }
                });
        return result;
    }

    @Override
    public @Nonnull
    Registration registerDispatchInterceptor(@Nonnull MessageDispatchInterceptor<? super CommandMessage<?>> dispatchInterceptor) {
        return super.registerDispatchInterceptor(dispatchInterceptor);
    }

    private RuntimeException asRuntime(Throwable e) {
        if (e instanceof Error) {
            throw (Error) e;
        } else if (e instanceof RuntimeException) {
            return (RuntimeException) e;
        } else {
            return new CommandExecutionException("An exception occurred while executing a command", e);
        }
    }

    /**
     * Builder class to instantiate a {@link DefaultCommandGateway}.
     * <p>
     * The {@code dispatchInterceptors} are defaulted to an empty list.
     * The {@link CommandBus} is a <b>hard requirements</b> and as such should be provided.
     */
    public static class Builder extends AbstractCommandGateway.Builder {
        private CommandCallback<Object, Object> commandCallback = new FailureLoggingCallback<>();

        @Override
        public Builder commandBus(@Nonnull CommandBus commandBus) {
            super.commandBus(commandBus);
            return this;
        }


        public Builder commandCallback(CommandCallback<Object, Object> commandCallback) {
            assertNonNull(commandCallback, "CommandCallback may not be null");
            this.commandCallback = commandCallback;
            return this;
        }

        @Override
        public Builder retryScheduler(@Nonnull RetryScheduler retryScheduler) {
            super.retryScheduler(retryScheduler);
            return this;
        }

        @Override
        public Builder dispatchInterceptors(
                MessageDispatchInterceptor<? super CommandMessage<?>>... dispatchInterceptors) {
            super.dispatchInterceptors(dispatchInterceptors);
            return this;
        }

        @Override
        public Builder dispatchInterceptors(
                List<MessageDispatchInterceptor<? super CommandMessage<?>>> dispatchInterceptors) {
            super.dispatchInterceptors(dispatchInterceptors);
            return this;
        }


        public DefaultCommandGateway build() {
            return new DefaultCommandGateway(this);
        }
    }
}
